/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * JSCFrame.java
 *
 * Created on 05.01.2009, 18:29:05
 */
package jsc;

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.io.*;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import org.bouncycastle.crypto.*;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.engines.TwofishEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Base64;

/**
 *
 * @author uli
 */
public class JSCFrame extends javax.swing.JFrame
{

    /** Creates new form JSCFrame */
    public JSCFrame()
    {
        /**
         * Start a thread to initalize the random number generator.
         * Doesn't nedd any GUI components so it can run while
         * the GUI is initalized
         */
        new Thread(new Runnable()
        {

            public void run()
            {
                initRandAndClasses();
            }
        }).start();

        initComponents();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        passwordLabel = new javax.swing.JLabel();
        passwordField = new javax.swing.JPasswordField();
        inputLabel = new javax.swing.JLabel();
        plaintextScrollPane = new javax.swing.JScrollPane();
        inputField = new javax.swing.JTextArea();
        ciphertextLabel = new javax.swing.JLabel();
        decryptCheckbox = new javax.swing.JCheckBox();
        okButton = new javax.swing.JButton();
        ciphertextScrollPane = new javax.swing.JScrollPane();
        outputField = new javax.swing.JTextArea();
        menuBar = new javax.swing.JMenuBar();
        fileMenu = new javax.swing.JMenu();
        loadFromFileMenuItem = new javax.swing.JMenuItem();
        saveToFileMenuItem = new javax.swing.JMenuItem();
        extrasMenu = new javax.swing.JMenu();
        copyToClipboardCheckboxMenuItem = new javax.swing.JCheckBoxMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle( i18n.getString("JSCFrame.title")); // NOI18N

        passwordLabel.setDisplayedMnemonic('p');
        passwordLabel.setText(i18n.getString("JSCFrame.passwordLabel.text")); // NOI18N

        inputLabel.setDisplayedMnemonic('i');
        inputLabel.setText(i18n.getString("JSCFrame.inputLabel.text")); // NOI18N

        inputField.setColumns(20);
        inputField.setLineWrap(true);
        inputField.setRows(5);
        plaintextScrollPane.setViewportView(inputField);
        inputField.getAccessibleContext().setAccessibleParent(outputField);

        ciphertextLabel.setDisplayedMnemonic('o');
        ciphertextLabel.setText(i18n.getString("JSCFrame.ciphertextLabel.text")); // NOI18N

        decryptCheckbox.setMnemonic('d');
        decryptCheckbox.setText(i18n.getString("JSCFrame.decryptCheckbox.text")); // NOI18N

        okButton.setMnemonic('o');
        okButton.setText(i18n.getString("JSCFrame.okButton.text")); // NOI18N
        okButton.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                okButtonMouseClicked(evt);
            }
        });

        outputField.setColumns(20);
        outputField.setEditable(false);
        outputField.setLineWrap(true);
        outputField.setRows(5);
        ciphertextScrollPane.setViewportView(outputField);
        outputField.getAccessibleContext().setAccessibleParent(okButton);

        fileMenu.setMnemonic('f');
        fileMenu.setText(i18n.getString("JSCFrame.fileMenu.text")); // NOI18N

        loadFromFileMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_O, java.awt.event.InputEvent.CTRL_MASK));
        loadFromFileMenuItem.setMnemonic('l');
        loadFromFileMenuItem.setText(i18n.getString("JSCFrame.loadFromFileMenuItem.text")); // NOI18N
        loadFromFileMenuItem.setToolTipText(i18n.getString("JSCFrame.loadFromFileMenuItem.toolTipText")); // NOI18N
        loadFromFileMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                loadFromFileMenuItemActionPerformed(evt);
            }
        });
        fileMenu.add(loadFromFileMenuItem);

        saveToFileMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK));
        saveToFileMenuItem.setMnemonic('s');
        saveToFileMenuItem.setText(i18n.getString("JSCFrame.saveToFileMenuItem.text")); // NOI18N
        saveToFileMenuItem.setToolTipText(i18n.getString("JSCFrame.saveToFileMenuItem.toolTipText")); // NOI18N
        saveToFileMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                saveToFileMenuItemActionPerformed(evt);
            }
        });
        fileMenu.add(saveToFileMenuItem);

        menuBar.add(fileMenu);

        extrasMenu.setMnemonic('e');
        extrasMenu.setText( i18n.getString("JSCFrame.extrasMenu.text")); // NOI18N

        copyToClipboardCheckboxMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Z, java.awt.event.InputEvent.CTRL_MASK));
        copyToClipboardCheckboxMenuItem.setMnemonic('c');
        copyToClipboardCheckboxMenuItem.setSelected(true);
        copyToClipboardCheckboxMenuItem.setText( i18n.getString("JSCFrame.copyToClipboardCheckboxMenuItem.text")); // NOI18N
        copyToClipboardCheckboxMenuItem.setToolTipText( i18n.getString("JSCFrame.copyToClipboardCheckboxMenuItem.toolTipText")); // NOI18N
        extrasMenu.add(copyToClipboardCheckboxMenuItem);

        menuBar.add(extrasMenu);

        setJMenuBar(menuBar);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(passwordLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(passwordField, javax.swing.GroupLayout.DEFAULT_SIZE, 217, Short.MAX_VALUE))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(inputLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(plaintextScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 246, Short.MAX_VALUE))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(ciphertextLabel)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                            .addGroup(layout.createSequentialGroup()
                                .addGap(1, 1, 1)
                                .addComponent(decryptCheckbox)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(okButton, javax.swing.GroupLayout.DEFAULT_SIZE, 171, Short.MAX_VALUE))
                            .addGroup(layout.createSequentialGroup()
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(ciphertextScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 235, Short.MAX_VALUE)))))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(inputLabel)
                    .addComponent(plaintextScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 130, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(passwordLabel)
                    .addComponent(passwordField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(decryptCheckbox)
                    .addComponent(okButton))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(ciphertextLabel)
                    .addComponent(ciphertextScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 129, Short.MAX_VALUE))
                .addContainerGap())
        );

        passwordField.getAccessibleContext().setAccessibleParent(inputField);
        okButton.getAccessibleContext().setAccessibleParent(passwordField);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void okButtonMouseClicked(java.awt.event.MouseEvent evt)//GEN-FIRST:event_okButtonMouseClicked
    {//GEN-HEADEREND:event_okButtonMouseClicked
        if (decryptCheckbox.isSelected())
        {
            decryptSymmetric();
        }
        else
        {
            encryptSymmetric();
            /**
             * Copy the output field's contents into the system clipboard if enables
             */
            if (copyToClipboardCheckboxMenuItem.isSelected())
            {
                Transferable outputText = new StringSelection(outputField.getText());
                systemClipboard.setContents(outputText, null);
            }
        }
    }//GEN-LAST:event_okButtonMouseClicked

    private void decryptSymmetric()
    {
        try
        {
            BlockCipher engine = new TwofishEngine();
            BufferedBlockCipher cipher =
                    new PaddedBufferedBlockCipher(new CBCBlockCipher(engine));
            //Get data and encrypt
            byte[] passwordBytes = new String(passwordField.getPassword()).getBytes();
            byte[] input;
            byte[] output;

            //Base64-decode the ciphertext
            input = Base64.decode(inputField.getText().getBytes());

            //Generate the salt
            byte[] salt = new byte[8];
            System.arraycopy(input, 0, salt, 0, 8);

            //Hash the password together with the salt
            Digest digest = new SHA256Digest();
            digest.update(salt, 0, salt.length);
            digest.update(passwordBytes, 0, passwordBytes.length);
            byte[] hashedKey = new byte[32];
            //Do the actual encryption
            digest.doFinal(hashedKey, 0);
            //Init cipher
            cipher.init(false, new KeyParameter(hashedKey));
            /**
             * Do the actual encryption:
             * input.length-8:
             *  the input array also contains the seed which is 8 bytes long
             *  if decrypting
             */
            int outputLen = 0;
            output = new byte[cipher.getOutputSize(input.length - 8)];
            outputLen = cipher.processBytes(input, 8, input.length - 8, output, 0);
            cipher.doFinal(output, outputLen);
            //Print the output into outputField and Base64-encode if we have to encrypt
            outputField.setText(new String(output));
        }
        catch (DataLengthException ex)
        {
            Logger.getLogger(JSCFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (IllegalStateException ex)
        {
            Logger.getLogger(JSCFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (InvalidCipherTextException ex)
        {
            Logger.getLogger(JSCFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void encryptSymmetric()
    {
        try
        {
            /**
             * JSC is using the Bouncy Castle Lightweight API.
             * Specification of the output:
             * -8 Bytes Salt
             * -Encryption bytes (should contain the IV)
             *
             * The whole block is Base64 encoded
             */
            BlockCipher engine = new TwofishEngine();
            BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine));
            //Get data and encrypt
            byte[] passwordBytes = new String(passwordField.getPassword()).getBytes();
            byte[] input;
            byte[] output;
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            //Copy the input text bytes into the input byte array
            input = inputField.getText().getBytes();

            //Generate the salt
            byte[] salt = new byte[8];
            srand.nextBytes(salt);

            //Hash the password together with the salt
            Digest digest = new SHA256Digest();
            digest.update(salt, 0, salt.length);
            digest.update(passwordBytes, 0, passwordBytes.length);
            byte[] hashedKeys = new byte[32];
            digest.doFinal(hashedKeys, 0);
            //Init cipher
            cipher.init(true, new KeyParameter(hashedKeys));
            //Do the actual encryption
            int outputLen = 0;
            output = new byte[cipher.getOutputSize(input.length)];
            outputLen = cipher.processBytes(input, 0, input.length, output, 0);
            cipher.doFinal(output, outputLen);

            bout.write(salt);
            bout.write(output);
            //Print the Base64-encoded output into the output field
            outputField.setText(new String(Base64.encode(bout.toByteArray())));
        }
        catch (IOException ex)
        {
            Logger.getLogger(JSCFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (CryptoException ex) //Data not decryptible
        {
            JOptionPane.showMessageDialog(this, "Encryption error", "Decryption impossible", JOptionPane.ERROR_MESSAGE);
            ex.printStackTrace();
        }
    }

    private void loadFromFileMenuItemActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_loadFromFileMenuItemActionPerformed
    {//GEN-HEADEREND:event_loadFromFileMenuItemActionPerformed
        FileInputStream fin = null;
        try
        {
            fileChooser.showOpenDialog(this);
            File file = fileChooser.getSelectedFile();
            byte[] buffer = new byte[(int) file.length()];
            fin = new FileInputStream(file);
            fin.read(buffer);
            fin.close();
            inputField.setText(new String(buffer));
        }
        catch (IOException ex)
        {
            JOptionPane.showMessageDialog(this, ex.getLocalizedMessage(), i18n.getString("IO_Error"), JOptionPane.ERROR_MESSAGE);
        }
        finally
        {
            try
            {
                fin.close();
            }
            catch (IOException ex)
            {
                Logger.getLogger(JSCFrame.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
}//GEN-LAST:event_loadFromFileMenuItemActionPerformed

    /**
     * Initializes the random number generator member variable
     * and loads the used BouncyCastle classes
     */
    private void initRandAndClasses()
    {
        srand = new SecureRandom();
        //Load the Bouncy Castle classes (performance tweak
        try
        {
            Class.forName("org.bouncycastle.crypto.engines.TwofishEngine");
            Class.forName("org.bouncycastle.crypto.modes.CBCBlockCipher");
            Class.forName("org.bouncycastle.crypto.BufferedBlockCipher");
            Class.forName("org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher");
            Class.forName("org.bouncycastle.util.encoders.Base64");
            Class.forName("org.bouncycastle.crypto.Digest");
            Class.forName("org.bouncycastle.crypto.digests.SHA256Digest");
            Class.forName("org.bouncycastle.crypto.params.KeyParameter");
        }
        catch (ClassNotFoundException ex)
        {
            Logger.getLogger(JSCFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void saveToFileMenuItemActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_saveToFileMenuItemActionPerformed
    {//GEN-HEADEREND:event_saveToFileMenuItemActionPerformed
        FileOutputStream fout = null;
        try
        {
            fileChooser.showSaveDialog(this);
            File file = fileChooser.getSelectedFile();
            byte[] buffer = outputField.getText().getBytes();
            fout = new FileOutputStream(file);
            fout.write(buffer);
            fout.close();
        }
        catch (IOException ex)
        {
            Logger.getLogger(JSCFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
        finally
        {
            try
            {
                fout.close();
            }
            catch (IOException ex)
            {
                Logger.getLogger(JSCFrame.class.getName()).log(Level.SEVERE, null, ex);
            }
            catch (NullPointerException ex)
            {
            }
        }
}//GEN-LAST:event_saveToFileMenuItemActionPerformed

    /**
     * @param args the command line arguments
     */
    public static void main(String args[])
    {
        java.awt.EventQueue.invokeLater(new Runnable()
        {

            public void run()
            {
                new JSCFrame().setVisible(true);
            }
        });
    }
    private JFileChooser fileChooser = new JFileChooser();
    private ResourceBundle i18n = ResourceBundle.getBundle("jsc/Bundle"); //NOI18N
    private SecureRandom srand = null;
    private Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel ciphertextLabel;
    private javax.swing.JScrollPane ciphertextScrollPane;
    private javax.swing.JCheckBoxMenuItem copyToClipboardCheckboxMenuItem;
    private javax.swing.JCheckBox decryptCheckbox;
    private javax.swing.JMenu extrasMenu;
    private javax.swing.JMenu fileMenu;
    private javax.swing.JTextArea inputField;
    private javax.swing.JLabel inputLabel;
    private javax.swing.JMenuItem loadFromFileMenuItem;
    private javax.swing.JMenuBar menuBar;
    private javax.swing.JButton okButton;
    private javax.swing.JTextArea outputField;
    private javax.swing.JPasswordField passwordField;
    private javax.swing.JLabel passwordLabel;
    private javax.swing.JScrollPane plaintextScrollPane;
    private javax.swing.JMenuItem saveToFileMenuItem;
    // End of variables declaration//GEN-END:variables
}
